﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

using DirectShowLib;
using DirectShowLib.BDA;

namespace SetTopBox.BDA
{
    class FilterGraphTools
    {
        public static int VFW_E_NOT_CONNECTED = -2147220983;

        public static int PinCount = 0;

        public static IPin GetPinForMediaType(IMpeg2Demultiplexer demux, AMMediaType mediaType)
        {
            // First of all, check to see if there is a free pin on the demux already.
            /*IEnumPins enumPins;
            ((IBaseFilter)demux).EnumPins(out enumPins);
            PinDirection pinDir;
            IPin[] destPins = new IPin[1];
            IPin tmp;
            while (enumPins.Next(destPins.Length, destPins, IntPtr.Zero) == 0)
            {
                destPins[0].QueryDirection(out pinDir);

                if (pinDir == PinDirection.Output)
                {
                    Console.WriteLine("Found output pin");
                    AMMediaType mt = new AMMediaType();
                    int hr = destPins[0].ConnectionMediaType(mt);
                    if (hr == VFW_E_NOT_CONNECTED)
                    {
                        Console.WriteLine("MT major = " + mt.majorType);
                        Console.WriteLine("Media major = " + mediaType.majorType);
                        Console.WriteLine("MT sub = " + mt.subType);
                        Console.WriteLine("Media sub= " + mediaType.subType);

                        //if (mt.majorType == mediaType.majorType && mt.subType == mediaType.subType)
                        //{
                        //demux.SetOutputPinMediaType(
                        return destPins[0];
                        //}
                    }
                    else
                    {
                        Console.WriteLine("HR = " + hr);
                    }
                }
            }*/

            // If not then create a new one for the right media type
            IPin tmp;
            demux.CreateOutputPin(mediaType, "" + PinCount++, out tmp);

            return tmp;
        }

        public static IBaseFilter AddFilterFromClsid(IGraphBuilder graphBuilder, Guid clsid, string name)
        {
            int hr = 0;
            IBaseFilter filter = null;

            if (graphBuilder == null)
                throw new ArgumentNullException("graphBuilder");

            try
            {
                Type type = Type.GetTypeFromCLSID(clsid);
                filter = (IBaseFilter)Activator.CreateInstance(type);

                hr = graphBuilder.AddFilter(filter, name);
                DsError.ThrowExceptionForHR(hr);
            }
            catch
            {
                if (filter != null)
                {
                    Marshal.ReleaseComObject(filter);
                    filter = null;
                }
            }

            return filter;
        }

        public static bool IsThisComObjectInstalled(Guid clsid)
        {
            bool retval = false;

            try
            {
                Type type = Type.GetTypeFromCLSID(clsid);
                object o = Activator.CreateInstance(type);
                retval = true;
                Marshal.ReleaseComObject(o);
            }
            catch { }

            return retval;
        }

        public static bool ConnectFilters(IGraphBuilder gBuilder, IPin srcPin, IBaseFilter destFilter)
        {
            PinDirection pinDir;
            srcPin.QueryDirection(out pinDir);

            if (pinDir != PinDirection.Output)
            {
                throw new ArgumentException();
            }

            IEnumPins destPins;
            IPin[] destPin = new IPin[1];
            destFilter.EnumPins(out destPins);
            IPin tmp;
            while (destPins.Next(destPin.Length, destPin, IntPtr.Zero) == 0)
            {
                destPin[0].QueryDirection(out pinDir);

                if (pinDir == PinDirection.Input)
                {
                    if (srcPin.ConnectedTo(out tmp) != 0)
                    {
                        if (gBuilder.ConnectDirect(srcPin, destPin[0], null) == 0)
                        {
                            Console.WriteLine("Connected!");
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public static bool ConnectFilters(IGraphBuilder gBuilder, IBaseFilter srcFilter, IBaseFilter destFilter)
        {
            IPin pin;
            return ConnectFilters(gBuilder, srcFilter, destFilter, out pin);
        }

        public static bool ConnectFilters(IGraphBuilder gBuilder, IBaseFilter srcFilter, IBaseFilter destFilter, out IPin srcPin)
        {
            return ConnectFilters(gBuilder, srcFilter, destFilter, Guid.Empty, out srcPin);
        }

        public static bool ConnectFilters(IGraphBuilder gBuilder, IBaseFilter srcFilter, IBaseFilter destFilter, Guid srcSubType, out IPin theSrcPin)
        {
            IEnumPins srcPins, destPins;
            IPin[] srcPin = new IPin[1], destPin = new IPin[1];

            srcFilter.EnumPins(out srcPins);
            destFilter.EnumPins(out destPins);
            PinDirection pinDir1, pinDir2;
            IPin tmp1, tmp2;
            while (srcPins.Next(srcPin.Length, srcPin, IntPtr.Zero) == 0)
            {
                srcPin[0].QueryDirection(out pinDir1);

                if (pinDir1 == PinDirection.Output)
                {
                    Console.WriteLine("Source pin");

                    if (srcSubType != Guid.Empty)
                    {
                        IEnumMediaTypes enumMediaTypes;

                        srcPin[0].EnumMediaTypes(out enumMediaTypes);

                        AMMediaType[] mTypes = new AMMediaType[1];

                        bool subtypeOK = false;

                        while (enumMediaTypes.Next(1, mTypes, IntPtr.Zero) == 0)
                        {
                            if (mTypes[0].subType == srcSubType)
                            {
                                subtypeOK = true;
                                break;
                            }
                        }

                        if (!subtypeOK)
                        {
                            continue;
                        }
                    }

                    if (srcPin[0].ConnectedTo(out tmp1) != 0)
                    {
                        while (destPins.Next(destPin.Length, destPin, IntPtr.Zero) == 0)
                        {
                            destPin[0].QueryDirection(out pinDir2);
                            if (pinDir2 == PinDirection.Input)
                            {
                                Console.WriteLine("Dest pin");

                                if (destPin[0].ConnectedTo(out tmp2) != 0)
                                {
                                    int res;
                                    if ((res = gBuilder.ConnectDirect(srcPin[0], destPin[0], null)) == 0)
                                    {
                                        Console.WriteLine("Connected!");
                                        theSrcPin = srcPin[0];
                                        return true;
                                    }
                                    else
                                    {
                                        Console.WriteLine("Continuing, res = " + res);
                                    }
                                }
                            }

                            Marshal.ReleaseComObject(destPin[0]);
                        }

                        if (destPins.Reset() != 0)
                        {
                            theSrcPin = null;
                            Console.WriteLine("Could not reset pins enumeration");
                            return false;
                        }
                    }
                }
                Marshal.ReleaseComObject(srcPin[0]);
            }
            theSrcPin = null;
            return false;
        }

        public static void Disconnect(IGraphBuilder gBuilder, IBaseFilter filter)
        {
            IFilterGraph fGraph = (IFilterGraph)gBuilder;
            IEnumPins srcPins;
            IPin[] srcPin = new IPin[1];

            filter.EnumPins(out srcPins);
            while (srcPins.Next(srcPin.Length, srcPin, IntPtr.Zero) == 0)
            {
                fGraph.Disconnect(srcPin[0]);
            }
            gBuilder.RemoveFilter(filter);
        }

        public static void Disconnect(IGraphBuilder gBuilder)
        {
            IFilterGraph fGraph = (IFilterGraph)gBuilder;
            IEnumFilters enumFilters;
            fGraph.EnumFilters(out enumFilters);
            IBaseFilter[] filters = new IBaseFilter[1];

            while (enumFilters.Next(filters.Length, filters, IntPtr.Zero) == 0)
            {
                IPin[] pins = new IPin[1];
                IEnumPins enumPins;
                filters[0].EnumPins(out enumPins);
                while (enumPins.Next(pins.Length, pins, IntPtr.Zero) == 0)
                {
                    fGraph.Disconnect(pins[0]);
                }
            }
        }

        public static IGraphBuilder GetGraphForDevice(Device device)
        {
            IFilterGraph2 fGraph = (IFilterGraph2)new FilterGraph();
            DVBSystemType sysType = device.Network;
            IBaseFilter bdaNetworkProvider = FilterGraphTools.AddFilterFromClsid(fGraph, typeof(DVBTNetworkProvider).GUID, "DVBT Network Provider");
            IDVBTuningSpace2 tSpace = (IDVBTuningSpace2)new DVBTuningSpace();
            tSpace.put_SystemType(sysType);
            tSpace.put_NetworkType("{216C62DF-6D7F-4E9A-8571-05F14EDB766A}");
            object bdaSource, bdaTuner;
            Guid guid = typeof(IBaseFilter).GUID;
            device.Source.BindToObject(null, null, ref guid, out bdaSource);
            FilterGraphTools.ConnectFilters(fGraph, bdaNetworkProvider, (IBaseFilter)bdaSource);
            device.Receiver.BindToObject(null, null, ref guid, out bdaTuner);
            FilterGraphTools.ConnectFilters(fGraph, (IBaseFilter)bdaSource, (IBaseFilter)bdaTuner);

            return fGraph;
        }

        public static void CreateDVBTTuneRequest(IDVBTuningSpace2 tSpace, out IDVBTuneRequest dvbTReq, int freq, int bw)
        {
            ITuneRequest tReq;
            tSpace.CreateTuneRequest(out tReq);
            dvbTReq = (IDVBTuneRequest)tReq;
            dvbTReq.put_ONID(-1);
            dvbTReq.put_SID(-1);
            dvbTReq.put_TSID(-1);
            IDVBTLocator locator = (IDVBTLocator)new DVBTLocator();
            locator.put_CarrierFrequency(freq);
            if (bw != 0)
            {
                locator.put_Bandwidth(bw);
            }
            dvbTReq.put_Locator(locator);
        }
    }
}
